/*
 *	Lightweight At Daemon
 *
 *	Compile with:
 *		gcc -s -Wall -Wstrict-prototypes atd.c -o atd
 *
 *	Copyright (C) 1996, Paul Gortmaker.
 *	Copyright (C) 2001, Russell Nelson
 *	Copyright (c) 2008, Ilya Petrov
 *
 *	Released under the GNU General Public License, version 2,
 *	included herein by reference.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <linux/pcap_rtc.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <sys/select.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <limits.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <dirent.h>
#include <syslog.h>


#define log(d,s,args...) syslog(d,s,##args)

void waitfor(time_t t) {
  int rtcfd, tfd, retval= 0;
  unsigned long data;
  struct timeval tv, rtc_tv;
  int nfds;
  fd_set afds;
  int setTime = 0; /*We don't have bool in C...*/
  time_t now;

  log(LOG_DEBUG,"waitfor %ld\n", t);

  rtcfd = open ("/dev/pcap_rtc", O_RDONLY);
  if (rtcfd ==  -1) {
    syslog(LOG_ERR, "/dev/pcap_rtc: %m\n");
    closelog();
    exit(errno);
  }

  tfd = open ("trigger", O_RDWR);

  if (tfd ==  -1) {
    syslog(LOG_ERR, "trigger: %m\n");
    closelog();
    exit(errno);
  }

  /* Read the RTC time/date */
  retval = ioctl(rtcfd, PCAP_RTC_IOC_GET_TIME, &rtc_tv);
  if (retval == -1) {
      syslog(LOG_ERR, "ioctl: %m\n");
      closelog();
      exit(errno);
  }

  syslog(LOG_DEBUG, "Current RTC date/time is %d sec %d usec\n",
      (int)rtc_tv.tv_sec, (int)rtc_tv.tv_usec );

  
  syslog(LOG_DEBUG, "Alarm date/time is %d sec\n", (int)t);

  if (t && t < rtc_tv.tv_sec) {
    log(LOG_DEBUG, "WOOOOT????\n"); 
    close(tfd);
    close(rtcfd);
    return;
  } else {
    log(LOG_DEBUG, "WOOOOT???? nope\n");
  }

  if (t) {

    rtc_tv.tv_sec = t;
    rtc_tv.tv_usec = 0;


    retval = ioctl(rtcfd, PCAP_RTC_IOC_SET_ALARM_TIME, &rtc_tv);
    if (retval == -1) {
      syslog(LOG_ERR, "ioctl: %m\n");
      closelog();
      exit(errno);
    }

    /* Read the current alarm settings */
    retval = ioctl(rtcfd, PCAP_RTC_IOC_GET_ALARM_TIME, &rtc_tv);
    if (retval == -1) {
      syslog(LOG_ERR, "ioctl: %m\n");
      closelog();
      exit(errno);
    }

    syslog(LOG_DEBUG, "Alarm time now set to %d sec %d usec",
           (int)rtc_tv.tv_sec, (int)rtc_tv.tv_usec );

  }

  syslog(LOG_DEBUG, "Waiting for alarm...");
  /* This blocks until the alarm ring causes an interrupt */
  FD_ZERO(&afds);
  FD_SET(tfd, &afds);

  nfds = tfd + 1;
  if (rtcfd > tfd) nfds = rtcfd + 1;

  /* Wait up to ten minutes. */
  tv.tv_sec = 10*60;
  tv.tv_usec = 0;
  if (select(nfds, &afds, (fd_set *) 0,  (fd_set *) 0, &tv) < 0) {
    if (errno != EINTR)
      {
	syslog(LOG_ERR, "select: %m\n");
	closelog();
      }
      exit(errno);
  }

  if (FD_ISSET(rtcfd, &afds)) {
    retval = read(rtcfd, &data, sizeof(unsigned long));
    if (retval == -1) {
      syslog(LOG_ERR, "read: %m\n");
      closelog();
      exit(errno);
    }
  }

  if (FD_ISSET(tfd, &afds)) {
      char buf[8];
      int i;
      retval = read(tfd, buf, 8);
      if (retval == -1) {
	  syslog(LOG_ERR, "read: %m\n");
	  closelog();
	  exit(errno);
      }
      syslog(LOG_DEBUG, "TRIGGER %d ", retval);
      for ( i = 0; i < retval; i++ )
	  if (( setTime = ( buf[i] == 'W' ) ))
	      break;
  }
  syslog(LOG_DEBUG, " okay. Alarm rang.\n");


  if ( setTime ) {
      //  Set the RTC time/date 
      syslog(LOG_DEBUG, "Setting RTC\n");
      now = time(NULL);

      rtc_tv.tv_sec = now;
      rtc_tv.tv_usec = 0;

      retval = ioctl(rtcfd, PCAP_RTC_IOC_SET_TIME, &rtc_tv);

      if (retval == -1) {
	  syslog(LOG_ERR, "ioctl: %m\n");
	  closelog();
	  exit(errno);
      }
  }

  close(rtcfd);
  close(tfd);

}


int runjob(char *fn)
{
  pid_t pid;
  int status;

  pid = fork();
  if (pid == -1)
    return -1;
  if (pid == 0) {
    syslog(LOG_DEBUG, "running %s\n", fn);
    execl(fn, fn, NULL);
    exit(127);
  }
  do {
    syslog(LOG_DEBUG, "waiting for %d\n", pid);
    if (waitpid(pid, &status, 0) == -1) {
      if (errno != EINTR)
        return -1;
      }
    break;
  } while(1);
  return 0;
}

unsigned long nameok(const char *name)
{
  char *middle;
  char *end;
  unsigned long that;

  /* check for timestamp.pid */
  that = strtol(name, &middle, 10);
  if (*middle != '.')
    return 0;
  strtol(middle+1, &end, 10);
  if (name + strlen(name) != end)
    return 0;
  return that;
}

void die(char *s)
{
  syslog(LOG_ERR, "%s\n", s);
  closelog();
  exit(2);
}

static void enable_rtc_wakeup(void)
{

}

int main(int argc, char *argv[]) {
  struct dirent *dirent;
  DIR *dir;
  unsigned long that, next, now;

  if (argc != 2) {
    fprintf(stderr, "usage: atd spooldir\n");
    exit(2);
  }
  {
    int	pid;
    pid = fork();
    if (pid > 0) {
      exit(0);
    } else if (pid < 0) {
      exit(-1);
    }
  }
  if (setsid() == -1) {
    exit(-1);
  }
  close(0);
  close(1);
  close(2);
  openlog("atd", LOG_PID, LOG_DAEMON);

  if (chdir(argv[1]) < 0) die("cannot chdir");
  if (mkfifo("trigger.new", 0777) < 0) die("cannot mkfifo trigger.new");
  if (rename("trigger.new","trigger")) die("cannot rename trigger.new");

  enable_rtc_wakeup();

  while(1) {
    /* run all the jobs in the past */
    now = time(NULL);
    dir = opendir(".");
    while ((dirent = readdir(dir))) {
      that = nameok(dirent->d_name);
      /* run jobs which are in the past, and find the next job in the future */
      /* avoid race conditions. Run jobs scheduled for the next second now */
      if (that)
        if (that <= now + 1) runjob(dirent->d_name);
    }
    closedir(dir);

    /* find the next job in the future.  A job we just ran might have
     * rescheduled itself.  */
    dir = opendir(".");
    next = ULONG_MAX;
    while ((dirent = readdir(dir))) {
      that = nameok(dirent->d_name);
      /* schedule jobs that we haven't just run */
      if (that) 
        if (that > now + 1)
          if (that < next) next = that;
    }
    closedir(dir);

    syslog(LOG_DEBUG, "next: %ld\n", next);
    if (next == ULONG_MAX) next = 0;
    waitfor(next);
  }
  closelog();
  return 0;
} 
